package xyz.xtt.equity.service.impl;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONUtil;
import xyz.xtt.asset.client.request.AssetNoInfoRequest;
import xyz.xtt.asset.client.response.TblAssetNoDto;
import xyz.xtt.common.starter.cache.CacheManager;
import xyz.xtt.equity.api.dto.PackageDto;
import xyz.xtt.equity.api.dto.TblUserBenefitByAssetIdDto;
import xyz.xtt.equity.api.dto.TblUserBenefitByPackageDto;
import xyz.xtt.equity.api.enums.BenefitEnum;
import xyz.xtt.equity.api.enums.BenefitPurchasePayStatusEnum;
import xyz.xtt.equity.api.enums.UserBenefitSourceTypeEnum;
import xyz.xtt.equity.client.request.TblUserBenefitDto;
import xyz.xtt.equity.constants.AssetCache;
import xyz.xtt.equity.constants.ErrorMessage;
import xyz.xtt.equity.entity.BenefitMetaData;
import xyz.xtt.equity.entity.TblAssetBenefitPackage;
import xyz.xtt.equity.entity.TblBenefit;
import xyz.xtt.equity.entity.TblBenefitPackage;
import xyz.xtt.equity.entity.TblBenefitPurchaseRecord;
import xyz.xtt.equity.entity.TblUserBenefit;
import xyz.xtt.equity.entity.TblUserBenefitChangeRecord;
import xyz.xtt.equity.exception.ChainException;
import xyz.xtt.equity.mapper.TblBenefitMapper;
import xyz.xtt.equity.mapper.TblBenefitPackageMapper;
import xyz.xtt.equity.service.IInnerService;
import xyz.xtt.equity.service.ITblAssetBenefitPackageService;
import xyz.xtt.equity.service.ITblBenefitPackageService;
import xyz.xtt.equity.service.ITblBenefitPurchaseRecordService;
import xyz.xtt.equity.service.ITblBenefitService;
import xyz.xtt.equity.service.ITblUserBenefitChangeRecordService;
import xyz.xtt.equity.service.ITblUserBenefitService;

/**
 * <p>
 * 权益信息表 服务实现类
 * </p>
 *
 * @author dale
 * @since 2025-02-10
 */
@RefreshScope
@Service
public class TblBenefitServiceImpl extends ServiceImpl<TblBenefitMapper, TblBenefit> implements ITblBenefitService {
	private static final LocalDateTime MAX_EXPIRE_TIME = LocalDateTime.of(9999, 12, 31, 23, 59, 59);
	private static final String USER_BENEFIT_CONSUME_CACHE = "userBenefitConsumeCache:%d:%s:%d";
	private static final String USER_DAILY_MINT_RECEIVE_CACHE = "nft:userDailyMintBenefitReceiveCache:%d";
	@Autowired
	private ITblUserBenefitService tblUserBenefitService;
	@Autowired
	private CacheManager cacheManager;
	@Autowired
	private ITblBenefitPackageService iTblBenefitPackageService;
	@Autowired
	private ITblBenefitPurchaseRecordService tblBenefitPurchaseRecordService;
	@Autowired
	private ITblUserBenefitChangeRecordService tblUserBenefitChangeRecordService;
	@Autowired
	private TblBenefitPackageMapper tblBenefitPackageMapper;
	@Autowired
	private ITblAssetBenefitPackageService iTblAssetBenefitPackageService;

	@Value("${business.benefit.type.ids}")
	private String businessBenefitTypeIds;
	@Autowired
	private IInnerService iInnerService;

	@Override
	public List<TblBenefit> queryBenefitsByPackageIds(List<Long> packageIds) {
		List<TblBenefit> benefits = new ArrayList<>();
		return null;
	}

	@Override
	public List<TblBenefit> queryBenefitsByPackageId(Long packageId) {
		return cacheManager.cacheList(() -> {
			LambdaQueryChainWrapper<TblBenefit> queryChainWrapper = new LambdaQueryChainWrapper<>(baseMapper);
			return queryChainWrapper.eq(TblBenefit::getPackageId, packageId).eq(TblBenefit::getIsDel, 0).list();
		}, AssetCache.BENEFIT_LIST_BY_PACKAGE, String.valueOf(packageId));
	}

	/**
	 * 获取3日内VIP将要过期的用户id集合
	 *
	 * @param pageNo      页码
	 * @param pageSize    每页数量
	 * @param benefitCode 权益code
	 * @return
	 */
	@Override
	public List<TblUserBenefitDto> getExpiringUserBenefits(int pageNo, int pageSize, String benefitCode) {
		LocalDateTime startTime = LocalDateTime.now().plusMinutes(10);
		LocalDateTime endTime = LocalDateTime.now().plusDays(3);
		LambdaQueryChainWrapper<TblUserBenefit> query = tblUserBenefitService
				.lambdaQuery()
				.eq(TblUserBenefit::getBenefitCode, benefitCode)
				.ge(TblUserBenefit::getExpireAt, startTime)
				.lt(TblUserBenefit::getExpireAt, endTime)
				.orderByAsc(TblUserBenefit::getExpireAt);
		Page<TblUserBenefit> page = query.page(new Page<>(pageNo, pageSize));
		if (page.getTotal() > 0) {
			return page.getRecords().stream().map(this::convertUserBenefit2Dto).toList();
		}
		return Collections.emptyList();
	}

	private TblUserBenefitDto convertUserBenefit2Dto(TblUserBenefit benefit) {
		TblUserBenefitDto dto = new TblUserBenefitDto();
		BeanUtils.copyProperties(benefit, dto);
		return dto;
	}

	@Override
	public Map<String, Integer> getUserBenefits(long appUserId, String benefitCodes) {
		// TODO Auto-generated method stub
		return null;
	}

	/**
	 * 权益发放同时记录权益领取记录
	 *
	 * @param packageCode
	 * @param appUserId
	 * @param sendNum
	 * @param sourceTypeEnum
	 * @param sourceRefId
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public void recordAndSendFreeBenefitPackage(String packageCode, long appUserId, int sendNum, UserBenefitSourceTypeEnum sourceTypeEnum, Long sourceRefId) {
		TblBenefitPackage benefitPackage = iTblBenefitPackageService.getPackageByCode(packageCode);
		if (benefitPackage == null) {
			throw new ChainException(ErrorMessage.BENEFIT_PACKAGE_NOT_EXIST);
		}
		LocalDateTime now = LocalDateTime.now();
		TblBenefitPurchaseRecord purchaseRecord = TblBenefitPurchaseRecord
				.builder()
				.recordId(IdUtil.getSnowflakeNextId())
				.appUserId(appUserId)
				.packageId(benefitPackage.getPackageId())
				.purchaseNum(sendNum)
				.unitPrice(BigDecimal.ZERO)
				.totalPaid(BigDecimal.ZERO)
				.payStatus(BenefitPurchasePayStatusEnum.PAID.code())
				.createTime(now)
				.updateTime(now)
				.isDel(0)
				.biz_type(sourceTypeEnum.name())
				.build();
		tblBenefitPurchaseRecordService.save(purchaseRecord);

		this.sendBenefitPackage(benefitPackage, appUserId, sendNum, sourceTypeEnum, sourceRefId);
	}

	@Override
	public void sendBenefitPackage(TblBenefitPackage benefitPackage, long appUserId, int sendNum, UserBenefitSourceTypeEnum sourceTypeEnum, Long sourceRefId) {
		List<TblBenefit> benefits = queryBenefitsByPackageId(benefitPackage.getPackageId());
		List<TblUserBenefit> userBenefits = new ArrayList<>();
		List<TblUserBenefitChangeRecord> changeRecords = new ArrayList<>();
		LocalDateTime now = LocalDateTime.now();
		long batchId = IdUtil.getSnowflakeNextId();
		benefits.stream().forEach(benefit -> {
			BenefitMetaData metaData = JSONUtil.toBean(benefit.getMetaJson(), BenefitMetaData.class);
			LocalDateTime startAt, expireAt;
			if (Objects.equals(metaData.getStartAtType(), 2)) {
				// 从当天零时开始
				startAt = LocalDateTime.of(LocalDate.now(), LocalTime.MIN);
			} else if (Objects.equals(metaData.getStartAtType(), 1)) {
				// 从当前时刻开始
				startAt = LocalDateTime.now();
			} else {
				// 从同benefit_code最近一条记录的失效时间开始
				BenefitEnum benefitEnum = BenefitEnum.getByCode(benefit.getBenefitCode());
				if (benefitEnum == null) {
					throw new ChainException(ErrorMessage.BENEFIT_NOT_EXIST);
				}
				TblUserBenefit latestUserBenefit = tblUserBenefitService.queryLatestUserBenefit(appUserId, benefitEnum);
				// 已经存在生效期内的权益时，从上一条记录的过期时间开始计算新的有效期
				if (latestUserBenefit != null && latestUserBenefit.getExpireAt().isAfter(LocalDateTime.now())) {
					startAt = latestUserBenefit.getExpireAt();
				} else {
					startAt = LocalDateTime.now();
				}
			}
			if (Objects.equals(metaData.getLimitDays(), -1)) {
				// 如果权益有效期为-1，则权益有效期为永久。过期时间为9999-12-31 23:59:59
				expireAt = MAX_EXPIRE_TIME;
			} else {
				// 如果上一条权益是永久有效，则当前权益的过期时间仍为永久有效。避免超过上限
				if (Objects.equals(startAt, MAX_EXPIRE_TIME)) {
					expireAt = MAX_EXPIRE_TIME;
				} else {
					expireAt = startAt.plusDays(metaData.getLimitDays() * sendNum);
				}
			}

			long userBenefitId = IdUtil.getSnowflakeNextId();
			userBenefits
					.add(TblUserBenefit
							.builder()
							.userBenefitId(userBenefitId)
							.appUserId(appUserId)
							.benefitId(benefit.getBenefitId())
							.benefitCode(benefit.getBenefitCode())
							.benefitNum(metaData.getLimitCount() * sendNum)
							.benefitRemain(metaData.getLimitCount() * sendNum)
							.startTime(startAt)
							.expireAt(expireAt)
							.sourceType(sourceTypeEnum.name())
							.sourceRefId(sourceRefId)
							.createTime(now)
							.updateTime(now)
							.isDel(0)
							.build());
			changeRecords
					.add(TblUserBenefitChangeRecord
							.builder()
							.recordId(IdUtil.getSnowflakeNextId())
							.batchId(batchId)
							.appUserId(appUserId)
							.userBenefitId(userBenefitId)
							.changeType(1)
							.changeAmount(metaData.getLimitCount() * sendNum)
							.beforeChange(0)
							.afterChange(metaData.getLimitCount() * sendNum)
							.operateDesc(sourceTypeEnum.getDesc())
							.operateBusinessId(sourceRefId)
							.createTime(now)
							.updateTime(now)
							.isDel(0)
							.build());
		});
		tblUserBenefitService.saveBatch(userBenefits);
		tblUserBenefitChangeRecordService.saveBatch(changeRecords);
	}

	@Override
	public List<TblBenefitPackage> getBusinessCastingBenefit() {
		List<String> packageIds = Arrays.asList(businessBenefitTypeIds.split(","));
		return cacheManager.cacheList(() -> {
			LambdaQueryWrapper<TblBenefitPackage> queryWrapper = new LambdaQueryWrapper<>();
			queryWrapper.in(TblBenefitPackage::getPackageId, packageIds).orderByDesc(TblBenefitPackage::getCreateTime);
			return tblBenefitPackageMapper.selectList(queryWrapper);
		}, AssetCache.BUSINESS_BENEFIT_TYPE);
	}

	@Override
	public TblUserBenefitByAssetIdDto getBenefitDetailInfo(String uid, String assetId) {
		org.springframework.util.Assert.isTrue(StringUtils.isNotBlank(uid) || StringUtils.isNotBlank(assetId), "uid and assetId must not be null");
		TblUserBenefitByAssetIdDto result = new TblUserBenefitByAssetIdDto();
		AssetNoInfoRequest assetNoInfoRequest = new AssetNoInfoRequest();
		assetNoInfoRequest.setAssetId(String.valueOf(assetId));
		assetNoInfoRequest.setOwnerId(String.valueOf(uid));
		List<TblAssetNoDto> data = iInnerService.getNFTNoInfo(assetNoInfoRequest);
		if (CollectionUtil.isNotEmpty(data)) {
			List<Long> list = data.stream().map(TblAssetNoDto::getId).toList();
			// 通过编号查找用户权益下单记录 一对多
			List<TblBenefitPurchaseRecord> benefitPurchaseRecords = tblBenefitPurchaseRecordService
					.lambdaQuery()
					.in(TblBenefitPurchaseRecord::getAssetNoId, list)
					.eq(TblBenefitPurchaseRecord::getAppUserId, Long.valueOf(uid))
					.orderByDesc(TblBenefitPurchaseRecord::getCreateTime)
					.list();
			// 过滤数据
			Map<String, TblBenefitPurchaseRecord> purchaseMap = new HashMap<>();
			for (TblBenefitPurchaseRecord benefitPurchaseRecord : benefitPurchaseRecords) {
				String key = benefitPurchaseRecord.getAssetNoId() + "_" + benefitPurchaseRecord.getAppUserId() + "_" + benefitPurchaseRecord.getPackageId();
				if (purchaseMap.containsKey(key)) {
					continue;
				}
				purchaseMap.put(key, benefitPurchaseRecord);
			}
			benefitPurchaseRecords = new ArrayList<>(purchaseMap.values());

			Map<Long, List<TblBenefitPurchaseRecord>> purchaseRecordMap = benefitPurchaseRecords
					.stream()
					.collect(Collectors.groupingBy(TblBenefitPurchaseRecord::getAssetNoId));
			List<Long> tblBenefitPurchaseRecordIds = benefitPurchaseRecords.stream().map(TblBenefitPurchaseRecord::getRecordId).toList();
			Map<Long, List<TblUserBenefit>> collect;
			if (!CollectionUtils.isEmpty(tblBenefitPurchaseRecordIds)) {
				List<TblUserBenefit> list1 = tblUserBenefitService
						.lambdaQuery()
						.in(TblUserBenefit::getBenefitPurchaseRecordId, tblBenefitPurchaseRecordIds)
						.list();
				collect = list1.stream().collect(Collectors.groupingBy(TblUserBenefit::getBenefitPurchaseRecordId));
			} else {
				collect = Map.of();
			}
			data.forEach(assetNoDto -> {
				LinkedHashMap<String, List<TblUserBenefitByPackageDto>> userBenefitMap = result.getUserBenefitMap();
				userBenefitMap.computeIfAbsent(assetNoDto.getAssetNo(), k -> new ArrayList<>());
				if (purchaseRecordMap.get(assetNoDto.getId()) == null) {
					return;
				}
				for (TblBenefitPurchaseRecord benefitPurchaseRecord : purchaseRecordMap.get(assetNoDto.getId())) {
					List<TblUserBenefit> userBenefits = collect.get(benefitPurchaseRecord.getRecordId());
					if (userBenefits == null || userBenefits.isEmpty()) {
						continue;
					}
					Long packageId = benefitPurchaseRecord.getPackageId();
					TblBenefitPackage packageById = iTblBenefitPackageService.getPackageById(packageId);
					TblUserBenefitByPackageDto tblUserBenefitByPackageDto = new TblUserBenefitByPackageDto();
					PackageDto packageDto = new PackageDto();
					packageDto.setPackageId(packageId);
					packageDto.setPackageName(packageById.getPackageName());
					tblUserBenefitByPackageDto.getUserBenefitMap().put(packageDto, userBenefits.stream().map(this::convertUserBenefit2DtoByType).toList());
					userBenefitMap.get(assetNoDto.getAssetNo()).add(tblUserBenefitByPackageDto);
				}
			});
		} else {
			List<TblAssetBenefitPackage> byAssetId = iTblAssetBenefitPackageService.getByAssetId(Long.valueOf(assetId));
			if (CollectionUtils.isEmpty(byAssetId)) {
				return result;
			}
			Map<Long, TblBenefitPackage> packageMap = iTblBenefitPackageService
					.lambdaQuery()
					.in(TblBenefitPackage::getPackageId, byAssetId.stream().map(TblAssetBenefitPackage::getPackageId).toList())
					.list()
					.stream()
					.collect(Collectors.toMap(TblBenefitPackage::getPackageId, Function.identity()));
			byAssetId.forEach(assetBenefitPackage -> {
				List<TblBenefit> benefits = queryBenefitsByPackageId(assetBenefitPackage.getPackageId());
				result.getUserBenefitMap().computeIfAbsent("", k -> new ArrayList<>());
				List<TblUserBenefitByPackageDto> tblUserBenefitByPackageDtos = result.getUserBenefitMap().get("");
				TblUserBenefitByPackageDto tblUserBenefitByPackageDto = new TblUserBenefitByPackageDto();
				tblUserBenefitByPackageDtos.add(tblUserBenefitByPackageDto);
				LinkedHashMap<PackageDto, List<TblUserBenefitDto>> userBenefitMap = tblUserBenefitByPackageDto.getUserBenefitMap();
				List<TblUserBenefitDto> objects = new ArrayList<>();
				userBenefitMap
						.put(new PackageDto(
								assetBenefitPackage.getPackageId(),
								packageMap.containsKey(assetBenefitPackage.getPackageId()) ? packageMap.get(assetBenefitPackage.getPackageId()).getPackageName()
										: ""), objects);
				for (TblBenefit benefit : benefits) {
					BenefitMetaData metaData = JSONUtil.toBean(benefit.getMetaJson(), BenefitMetaData.class);
					result.getUserBenefitMap().computeIfAbsent("", k -> new ArrayList<>());
					BenefitEnum benefitEnum = BenefitEnum.getByCode(benefit.getBenefitCode());
					if (benefitEnum != null) {
						TblUserBenefitDto dto = new TblUserBenefitDto();
						dto.setIsDel(0);
						dto.setBenefitCode(benefit.getBenefitCode());
						dto.setBenefitId(benefit.getBenefitId());
						// 0:次数 1:时间 2 :次数+时间
						dto.setBenefitNum((benefitEnum.getValueType() & 1) == 1 ? metaData.getLimitDays() : metaData.getLimitCount());
						dto.setBenefitRemain((benefitEnum.getValueType() & 1) == 1 ? metaData.getLimitDays() : metaData.getLimitCount());
						objects.add(dto);
					}
				}

			});
		}
		return result;
	}

	private TblUserBenefitDto convertUserBenefit2DtoByType(TblUserBenefit benefit) {
		TblUserBenefitDto dto = new TblUserBenefitDto();
		BeanUtils.copyProperties(benefit, dto);
		if (dto.getBenefitRemain() == 0) {
			dto.setIsDel(1);
			return dto;
		}
		LocalDateTime now = LocalDateTime.now();
		// 说明要计算时间的权益
		if (dto.getBenefitRemain() < 0) {
			// 根据开始结束时间推算 剩余多少天 如果只有0 天 向上+1 否则正常取即可
			if (now.isAfter(dto.getExpireAt())) {
				dto.setIsDel(1);
				return dto;
			} else {
				dto.setBenefitRemain(Math.max(1, (int) ChronoUnit.DAYS.between(dto.getStartTime(), dto.getExpireAt())));
			}
		}
		return dto;
	}
}
